#include <dlfcn.h>
#include "qemu/osdep.h"
#include "trace.h"
#include "exec/log.h"
#include "qemu.h"
#include "user-internals.h"
#include "user-mmap.h"
#include <sys/syscall.h>

#if defined(__i386__)
#define HOSTAPI_CALL __attribute__((regparm(1)))
#else
#define HOSTAPI_CALL
#endif

enum qemu_host_event_t
{
    QEMU_HOST_UNKNOWN = 0,
    QEMU_HOST_DLOPEN,
    QEMU_HOST_DLSYM,
    QEMU_HOST_DLCLOSE,
    QEMU_HOST_MMAN,
};

struct qemu_event_context_t
{
    void* $state;
    union { enum qemu_host_event_t event; void* unused; };
};

static void *qemu_host_mmap(void *__addr, size_t __len, int __prot, int __flags, int __fd, off_t __offset)
{
    return (void*)target_mmap((abi_ulong)__addr, (abi_ulong)__len, __prot, __flags, __fd, (abi_ulong)__offset);
}

static void HOSTAPI_CALL qemu_host_handle_event(struct qemu_event_context_t* $context)
{
    switch ($context->event)
    {
    case QEMU_HOST_DLOPEN:
    {
        struct { struct qemu_event_context_t $context; const char* name; void* $ret; } *_ = (typeof(_))$context;
        _->$ret = dlopen(_->name, 2);
        break;
    }
    case QEMU_HOST_DLSYM:
    {
        struct { struct qemu_event_context_t $context; void* handle; const char* name; void* $ret; } *_ = (typeof(_))$context;
        _->$ret = dlsym(_->handle, _->name);
        break;
    }
    case QEMU_HOST_DLCLOSE:
    {
        struct { struct qemu_event_context_t $context; void* handle; } *_ = (typeof(_))$context;
        dlclose(_->handle);
        break;
    }
    case QEMU_HOST_MMAN:
    {
        struct { struct qemu_event_context_t $context; void *p_mmap, *p_munmap, *p_mprotect, *p_madvise; } *_ = (typeof(_))$context;
        _->p_mmap     = &qemu_host_mmap;
        _->p_munmap   = &target_munmap;
        _->p_mprotect = &target_mprotect;
        _->p_madvise  = &target_madvise;
        break;
    }
    default:
        break;
    }
}

void ca11c0de(CPUArchState* cpu_env, abi_long arg)
{
    typeof(qemu_host_handle_event)* func = *(typeof(func)*)arg;
    if (func)
    {
        *(CPUArchState**)arg = cpu_env;
        (*func)((struct qemu_event_context_t*)arg);
    }
    else
    {
        *(void**)arg = &qemu_host_handle_event;
    }
}

__attribute__((visibility ("hidden"))) void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset)
{
    return (void*)syscall(SYS_mmap, addr, length, prot, flags, fd, offset);
}

__attribute__((visibility ("hidden"))) int munmap(void *addr, size_t length)
{
    return (int)syscall(SYS_munmap, addr, length);
}